package cn.snmp.mutual.uk.stack;

import java.io.*;
import java.util.*;
import cn.snmp.mutual.uk.event.RequestPduListener;
import cn.snmp.mutual.uk.event.TrapListener;
import cn.snmp.mutual.uk.pdu.DiscoveryPdu;
import cn.snmp.mutual.uk.util.SnmpUtilities;

public class SnmpContextv3Pool implements SnmpContextv3Face {
    private static final String version_id = "@(#)$Id: SnmpContextv3Pool.java,v 3.27 2009/03/05 13:27:41 birgita Exp $ Copyright Westhawk Ltd";
    protected static Hashtable contextPool;
    protected String hostname;
    protected String socketType;
    protected String bindAddr;
    protected int hostPort;
    protected SnmpContextv3 context;
    protected String userName;
    protected boolean useAuthentication;
    protected String userAuthenticationPassword;
    protected boolean usePrivacy;
    protected String userPrivacyPassword;
    protected int authenticationProtocol;
    protected byte[] contextEngineId;
    protected String contextName;
    protected UsmAgent usmAgent;
    protected boolean hasChanged;
    protected int privacyProtocol;

    public SnmpContextv3Pool(final String host, final int port) throws IOException {
        this(host, port, null, "Standard");
    }

    public SnmpContextv3Pool(final String host, final int port, final String typeSocket) throws IOException {
        this(host, port, null, typeSocket);
    }

    public SnmpContextv3Pool(final String host, final int port, final String bindAddress, final String typeSocket) throws IOException {
        this.context = null;
        this.userName = "";
        this.useAuthentication = false;
        this.userAuthenticationPassword = "";
        this.usePrivacy = false;
        this.userPrivacyPassword = "";
        this.authenticationProtocol = 0;
        this.contextEngineId = new byte[0];
        this.contextName = "";
        this.usmAgent = null;
        this.hasChanged = false;
        this.privacyProtocol = 2;
        initPools();
        this.hostname = host;
        this.hostPort = port;
        this.bindAddr = bindAddress;
        this.socketType = typeSocket;
    }

    private static synchronized void initPools() {
        if (SnmpContextv3Pool.contextPool == null) {
            SnmpContextv3Pool.contextPool = new Hashtable(5);
        }
    }

    public int getVersion() {
        return 3;
    }

    public String getHost() {
        return this.hostname;
    }

    public int getPort() {
        return this.hostPort;
    }

    public String getBindAddress() {
        return this.bindAddr;
    }

    public String getTypeSocket() {
        return this.socketType;
    }

    public String getSendToHostAddress() {
        String res = null;
        if (this.context != null) {
            res = this.context.getSendToHostAddress();
        }
        return res;
    }

    public String getReceivedFromHostAddress() {
        String res = null;
        if (this.context != null) {
            res = this.context.getReceivedFromHostAddress();
        }
        return res;
    }

    public String getUserName() {
        return this.userName;
    }

    public void setUserName(final String newUserName) {
        if (newUserName != null && !newUserName.equals(this.userName)) {
            this.userName = newUserName;
            this.hasChanged = true;
        }
    }

    public boolean isUseAuthentication() {
        return this.useAuthentication;
    }

    public void setUseAuthentication(final boolean newUseAuthentication) {
        if (newUseAuthentication != this.useAuthentication) {
            this.useAuthentication = newUseAuthentication;
            this.hasChanged = true;
        }
    }

    public String getUserAuthenticationPassword() {
        return this.userAuthenticationPassword;
    }

    public void setUserAuthenticationPassword(final String newUserAuthenticationPd) {
        if (newUserAuthenticationPd != null && !newUserAuthenticationPd.equals(this.userAuthenticationPassword)) {
            this.userAuthenticationPassword = newUserAuthenticationPd;
            this.hasChanged = true;
        }
    }

    public void setPrivacyProtocol(final int protocol) throws IllegalArgumentException {
        if (protocol == 3 || protocol == 2) {
            if (protocol != this.privacyProtocol) {
                this.privacyProtocol = protocol;
                this.hasChanged = true;
            }
            return;
        }
        this.hasChanged = false;
        throw new IllegalArgumentException("Privacy Protocol should be DES or AES");
    }

    public void setAuthenticationProtocol(final int protocol) throws IllegalArgumentException {
        if (protocol == 0 || protocol == 1) {
            if (protocol != this.authenticationProtocol) {
                this.authenticationProtocol = protocol;
                this.hasChanged = true;
            }
            return;
        }
        this.hasChanged = false;
        throw new IllegalArgumentException("Authentication Protocol should be MD5 or SHA1");
    }

    public int getPrivacyProtocol() {
        return this.privacyProtocol;
    }

    public int getAuthenticationProtocol() {
        return this.authenticationProtocol;
    }

    public boolean isUsePrivacy() {
        return this.usePrivacy;
    }

    public void setUsePrivacy(final boolean newUsePrivacy) {
        if (newUsePrivacy != this.usePrivacy) {
            this.usePrivacy = newUsePrivacy;
            this.hasChanged = true;
        }
    }

    public String getUserPrivacyPassword() {
        return this.userPrivacyPassword;
    }

    public void setUserPrivacyPassword(final String newUserPrivacyPd) {
        if (newUserPrivacyPd != null && !newUserPrivacyPd.equals(this.userPrivacyPassword)) {
            this.userPrivacyPassword = newUserPrivacyPd;
            this.hasChanged = true;
        }
    }

    public void setContextEngineId(final byte[] newContextEngineId) throws IllegalArgumentException {
        if (newContextEngineId != null) {
            if (!newContextEngineId.equals(this.contextEngineId)) {
                this.contextEngineId = newContextEngineId;
                this.hasChanged = true;
            }
            return;
        }
        this.hasChanged = false;
        throw new IllegalArgumentException("contextEngineId is null");
    }

    public byte[] getContextEngineId() {
        return this.contextEngineId;
    }

    public void setContextName(final String newContextName) {
        if (newContextName != null && !newContextName.equals(this.contextName)) {
            this.contextName = newContextName;
            this.hasChanged = true;
        }
    }

    public String getContextName() {
        return this.contextName;
    }

    public void setUsmAgent(final UsmAgent newAgent) {
        if (newAgent != null && newAgent != this.usmAgent) {
            this.usmAgent = newAgent;
            this.hasChanged = true;
        }
    }

    public UsmAgent getUsmAgent() {
        return this.usmAgent;
    }

    public boolean addDiscoveryPdu(final DiscoveryPdu pdu) throws IOException, PduException, IllegalArgumentException {
        if (this.hasChanged || this.context == null) {
            this.context = this.getMatchingContext();
        }
        return this.context.addDiscoveryPdu(pdu);
    }

    public boolean addPdu(final Pdu pdu) throws IOException, PduException, IllegalArgumentException {
        if (this.hasChanged || this.context == null) {
            this.context = this.getMatchingContext();
        }
        return this.context.addPdu(pdu);
    }

    public boolean removePdu(final int requestId) {
        boolean res = false;
        if (this.context != null) {
            res = this.context.removePdu(requestId);
        }
        return res;
    }

    public byte[] encodeDiscoveryPacket(final byte msg_type, final int rId, final int errstat, final int errind, final Enumeration ve, final Object obj) throws IOException, EncodingException {
        byte[] res = null;
        if (this.context != null) {
            res = this.context.encodeDiscoveryPacket(msg_type, rId, errstat, errind, ve, obj);
        }
        return res;
    }

    public byte[] encodePacket(final byte msg_type, final int rId, final int errstat, final int errind, final Enumeration ve, final Object obj) throws IOException, EncodingException {
        byte[] res = null;
        if (this.context != null) {
            res = this.context.encodePacket(msg_type, rId, errstat, errind, ve, obj);
        }
        return res;
    }

    public void sendPacket(final byte[] packet) {
        if (this.context != null) {
            this.context.sendPacket(packet);
        }
    }

    public void destroy() {
        synchronized (SnmpContextv3Pool.contextPool) {
            if (this.context != null) {
                final String hashKey = this.context.getHashKey();
                int count = 0;
                final SnmpContextPoolItem item = (SnmpContextPoolItem) SnmpContextv3Pool.contextPool.get(hashKey);
                if (item != null) {
                    count = item.getCounter();
                    --count;
                    item.setCounter(count);
                }
                if (count <= 0) {
                    SnmpContextv3Pool.contextPool.remove(hashKey);
                    this.context.destroy();
                }
                this.context = null;
            }
        }
    }

    public void destroyPool() {
        Hashtable copyOfPool = null;
        synchronized (SnmpContextv3Pool.contextPool) {
            synchronized (SnmpContextv3Pool.contextPool) {
                copyOfPool = (Hashtable) SnmpContextv3Pool.contextPool.clone();
            }
            SnmpContextv3Pool.contextPool.clear();
        }
        this.context = null;
        this.hasChanged = true;
        final Enumeration keys = copyOfPool.keys();
        while (keys.hasMoreElements()) {
            final String key = (String) keys.nextElement();
            final SnmpContextPoolItem item = (SnmpContextPoolItem) copyOfPool.get(key);
            if (item != null) {
                final SnmpContextBasisFace cntxt = item.getContext();
                cntxt.destroy();
            }
        }
        copyOfPool.clear();
    }

    public boolean isDestroyed() {
        boolean isDestroyed = true;
        if (this.context != null) {
            isDestroyed = this.context.isDestroyed();
        }
        return isDestroyed;
    }

    protected SnmpContextv3 getMatchingContext() throws IOException, IllegalArgumentException {
        SnmpContextPoolItem item = null;
        SnmpContextv3 newContext = null;
        final String hashKey = this.getHashKey();
        this.destroy();
        synchronized (SnmpContextv3Pool.contextPool) {
            int count = 0;
            if (SnmpContextv3Pool.contextPool.containsKey(hashKey)) {
                item = (SnmpContextPoolItem) SnmpContextv3Pool.contextPool.get(hashKey);
                newContext = (SnmpContextv3) item.getContext();
                count = item.getCounter();
            } else {
                newContext = new SnmpContextv3(this.hostname, this.hostPort, this.bindAddr, this.socketType);
                newContext.setContextEngineId(this.contextEngineId);
                newContext.setContextName(this.contextName);
                newContext.setUserName(this.userName);
                newContext.setUseAuthentication(this.useAuthentication);
                newContext.setUserAuthenticationPassword(this.userAuthenticationPassword);
                newContext.setAuthenticationProtocol(this.authenticationProtocol);
                newContext.setUsePrivacy(this.usePrivacy);
                newContext.setUserPrivacyPassword(this.userPrivacyPassword);
                newContext.setUsmAgent(this.usmAgent);
                newContext.setPrivacyProtocol(this.privacyProtocol);
                item = new SnmpContextPoolItem(newContext);
                SnmpContextv3Pool.contextPool.put(hashKey, item);
            }
            this.hasChanged = false;
            ++count;
            item.setCounter(count);
        }
        return newContext;
    }

    public void dumpContexts(final String title) {
        try {
            if (this.hasChanged) {
                this.context = this.getMatchingContext();
            }
        } catch (IOException exc) {
            if (AsnObject.debug > 0) {
                System.out.println(this.getClass().getName() + ".dumpContexts(): " + exc.getMessage());
            }
        }
        System.out.println(title + " " + SnmpContextv3Pool.contextPool.size() + " context(s)");
        final Enumeration keys = SnmpContextv3Pool.contextPool.keys();
        int i = 0;
        while (keys.hasMoreElements()) {
            final String key = (String) keys.nextElement();
            final SnmpContextPoolItem item = (SnmpContextPoolItem) SnmpContextv3Pool.contextPool.get(key);
            if (item != null) {
                final int count = item.getCounter();
                final SnmpContextv3 cntxt = (SnmpContextv3) item.getContext();
                if (cntxt == this.context) {
                    System.out.println("\tcurrent context: ");
                }
                System.out.println("\tcontext " + i + ": " + key + ", count: " + count + ", " + cntxt.toString() + "\n" + ", " + cntxt.getDebugString());
                ++i;
            }
        }
        System.out.println("\thasChanged: " + this.hasChanged);
    }

    public String getHashKey() {
        final StringBuffer buffer = new StringBuffer();
        buffer.append(this.hostname);
        buffer.append("_").append(this.hostPort);
        buffer.append("_").append(this.bindAddr);
        buffer.append("_").append(this.socketType);
        buffer.append("_").append(this.useAuthentication);
        buffer.append("_").append(SnmpContextv3Pool.ProtocolNames[this.authenticationProtocol]);
        buffer.append("_").append(SnmpContextv3Pool.ProtocolNames[this.privacyProtocol]);
        buffer.append("_").append(this.userAuthenticationPassword);
        buffer.append("_").append(this.userName);
        buffer.append("_").append(this.usePrivacy);
        buffer.append("_").append(this.userPrivacyPassword);
        buffer.append("_").append(SnmpUtilities.toHexString(this.contextEngineId));
        buffer.append("_").append(this.contextName);
        buffer.append("_v").append(this.getVersion());
        return buffer.toString();
    }

    public void addTrapListener(final TrapListener l) throws IOException {
        if (this.hasChanged || this.context == null) {
            this.context = this.getMatchingContext();
        }
        this.context.addTrapListener(l);
    }

    public void removeTrapListener(final TrapListener l) throws IOException {
        if (this.hasChanged || this.context == null) {
            this.context = this.getMatchingContext();
        }
        this.context.removeTrapListener(l);
    }

    public void addTrapListener(final TrapListener l, final int port) throws IOException {
        if (this.hasChanged || this.context == null) {
            this.context = this.getMatchingContext();
        }
        this.context.addTrapListener(l, port);
    }

    public void removeTrapListener(final TrapListener l, final int port) throws IOException {
        if (this.hasChanged || this.context == null) {
            this.context = this.getMatchingContext();
        }
        this.context.removeTrapListener(l, port);
    }

    public void addTrapListener(final TrapListener l, final ListeningContextPool lcontext) throws IOException {
        if (this.hasChanged || this.context == null) {
            this.context = this.getMatchingContext();
        }
        this.context.addTrapListener(l, lcontext);
    }

    public void removeTrapListener(final TrapListener l, final ListeningContextPool lcontext) throws IOException {
        if (this.hasChanged || this.context == null) {
            this.context = this.getMatchingContext();
        }
        this.context.removeTrapListener(l, lcontext);
    }

    public void addRequestPduListener(final RequestPduListener l) throws IOException {
        if (this.hasChanged || this.context == null) {
            this.context = this.getMatchingContext();
        }
        this.context.addRequestPduListener(l);
    }

    public void removeRequestPduListener(final RequestPduListener l) throws IOException {
        if (this.hasChanged || this.context == null) {
            this.context = this.getMatchingContext();
        }
        this.context.removeRequestPduListener(l);
    }

    public void addRequestPduListener(final RequestPduListener l, final int port) throws IOException {
        if (this.hasChanged || this.context == null) {
            this.context = this.getMatchingContext();
        }
        this.context.addRequestPduListener(l, port);
    }

    public void removeRequestPduListener(final RequestPduListener l, final int port) throws IOException {
        if (this.hasChanged || this.context == null) {
            this.context = this.getMatchingContext();
        }
        this.context.removeRequestPduListener(l, port);
    }

    public void addRequestPduListener(final RequestPduListener l, final ListeningContextPool lcontext) throws IOException {
        if (this.hasChanged || this.context == null) {
            this.context = this.getMatchingContext();
        }
        this.context.addRequestPduListener(l, lcontext);
    }

    public void removeRequestPduListener(final RequestPduListener l, final ListeningContextPool lcontext) throws IOException {
        if (this.hasChanged || this.context == null) {
            this.context = this.getMatchingContext();
        }
        this.context.removeRequestPduListener(l, lcontext);
    }

    public Pdu processIncomingPdu(final byte[] message) throws DecodingException, IOException {
        if (this.hasChanged || this.context == null) {
            this.context = this.getMatchingContext();
        }
        Pdu pdu = null;
        pdu = this.context.processIncomingPdu(message);
        return pdu;
    }

    @Override
    public String toString() {
        String res = "";
        try {
            if (this.hasChanged || this.context == null) {
                this.context = this.getMatchingContext();
            }
            res = this.context.toString();
        } catch (IOException exc) {
            if (AsnObject.debug > 0) {
                System.out.println(this.getClass().getName() + ".toString(): " + exc.getMessage());
            }
        }
        return res;
    }

    public Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }
}
